=begin rdoc
Events are logging information from a System under Test (SUT).
Events can be atomic actions, but also be procesed into complex,
abstracted events.

Author::    Matthias Woehrle  (mailto:matthias.woehrle@tik.ee.ethz.ch)
Copyright:: Copyright (c) 2008 ETH Zurich
License::   

TODO:

MISSING:


=end

$:.unshift File.dirname(__FILE__)
require 'periodic'



#  Event is a subclass of hash with some required fields
#  i) :type is the event type and is denoted by a symbol
#  ii) :nodeid is an integer or a string giving the id of an individual node in your distributed system
#  iii) Both keys must be added at event creation or a eventException is raised
#  iiii) All keys need to be symbols or a keyException is raised
class Event < Hash                                                             

  @@eventException = Exception.new("Event definition error: node/type missing")
  @@keyException = Exception.new("Event definition error: non-string key")
  #validkeys is only used in conjunction with constraints, so they only get invoked
  #when using the event specification with the with-class method
  #We could also do here lazy instantiation, which would generally save memory, but hurt performance
  @validkeys = [:type]
  @constraints = Hash.new

#class methods

  class << self; attr_accessor :validkeys, :constraints end



  #Callback from event subclasses creation: Used to add a specific template 
  #of an event with a given type and certain keys and constraints.
  #Only used when using subclasses of event for specific format definitions
  def self.inherited(subklass)
    puts "Created subclass #{subklass.name}" if $DEBUG
    subklass.instance_eval do
      @validkeys = [:type]
      @constraints = Hash.new
    end
  end
  
  #Event format definition: allows to specifiy event keys on a given event class
  #and to specify constraints on their values, such as the type of the attribute, which is one
  # of (:integer, :periodic, :string, :float)
  def self.with(hash)
    allowed_types = [:integer, :periodic, :string, :float, :hash]
    
    if value = hash.delete(:name) #note that returns nil (false) when not available
      p @validkeys if $DEBUG
      @validkeys.include?(value) || @validkeys << value
    else
      $stderr.puts "attribute name is missing in with statement called with keys #{hash.keys.to_a}"
      raise "EventAttributeError"
    end
      
    unless allowed_types.include?(hash[:fieldtype].to_sym)
      puts "#{hash[:fieldtype]} is not a valid type"
      raise "SpecificationException"
    end
    if hash[:fieldtype] == :periodic
      @periodic || @periodic = Hash.new()
      puts "@periodic[\"#{value}\"] = Periodic.new(#{hash[:range]})" if $DEBUG
      eval "@periodic[\"#{value}\"] = Periodic.new(#{hash[:range]})"
    end
    @constraints[value] = hash
    return self
  end
  
#instance methods  
  
  #Constructor mehtod with an array or a hash as as a parameter
  #An array specifies values for each of the event keys. This is used when Event 
  #is sub-classed for a specific template
  #A hash specifies keys and values. This is used when creating a new event from the plain event
  #without any template from scratch.
  def initialize(hash_or_array)
    #TODO discuss if node and type are required and how init is done and the call to
    #super like this does not seem to work => test!!!

    p hash_or_array if $DEBUG
    #Uf an Array is given, it is assumed that this specific event has specified
    #valid keys, which are now used for the correct value assignment
    if hash_or_array.kind_of?(Array)
      self.class.validkeys.each do |key| 
        #TODO: elaborate if do everything in here
        self[key] = hash_or_array.shift
      end
    else
         # if a hash was given, just merge in
         self.merge!(hash_or_array)
    end

#less checking, assuming user is a good citizen    
    #Checking that event has a nodeidentifier and a type
    #raise @@eventException unless(self.has_key?(:nodeid) and self.has_key?(:type))
    #Checking that type is a symbol
    #raise @@keyException if self.keys.find {|key| !key.instance_of?Symbol}
 
    if not  self.class.constraints.empty?
      self.keys.each do |key|
#        unless self.class.validkeys.include?(key)
#          $stderr.puts "given key #{key} is not valid for event #{self} of type #{self[:type]}"
#          raise "ValidKeyError"
#        end
        if self.class.constraints[key]
          if self.class.constraints[key][:notification]
            notification_level = self.class.constraints[key][:notification]
          else
            notification_level = :warning
          end
          
          thisfieldtype = self.class.constraints[key][:fieldtype]
          case thisfieldtype
          #TODO: to_i and to_f do not raise an exception so there might be a problem here
          #Because we convert anyway and we need to since everything is a String when we read it!
          #maybe do something with regualr expressions but that is expensive
            when :integer
              self[key] = self[key].to_i
              constraint_violation(:fieldtype, notification_level, key, self[key]) unless self[key].instance_of?Fixnum
            when :periodic
              #eval "#{self.class.class_eval{@type}.to_s.capitalize}_#{key}_periodic.order(self[key].to_i)"
              self[key] = self[key].to_i
            when :string
              constraint_violation(:fieldtype, notification_level, key, self[key]) unless self[key].instance_of?String
            when :float
              self[key] = self[key].to_f
              constraint_violation(:fieldtype, notification_level, key, self[key]) unless self[key].instance_of?Float
           #no else since I check for allowed types above
          end
          self.class.constraints[key].each do |constraint, value|  
            case constraint
               when :range
                 if value.kind_of?(Array) or value.kind_of?(Range)
                     constraint_violation(:range, notification_level, key, self[key]) unless value.include?self[key]
                 elsif  value.kind_of?(Fixnum) or value.kind_of?(Float) or value.kind_of?(String)
                     constraint_violation(:range, notification_level, key, self[key]) unless value.eql?(self[key])
                 elsif  value.kind_of?(Hash)
                     constraint_violation(:range, notification_level, key, self[key]) unless self[key].to_f > value[:lower].to_f and self[key].to_f < value[:upper].to_f
                 else
                  $stderr.puts "Some constraints types are not implemented yet such as #{value.class}"
                  raise "SpecificationException"                      
                 end
               else
                 if constraint != :fieldtype and constraint != :type and \
                   constraint != :notification and constraint != :nodeid
                   $stderr.puts "No constraint #{constraint} of type #{self[:type]}"
                  raise "SpecificationException"
                 end
            end
          end
        else
          if key != :type
            puts "No constraints on #{key}"
          end
        end
      end
    #Need to check whether this event has an instance variable (note that class eval concerns the instances)
     if self.class.class_eval {@periodic}
        keylist = self.class.class_eval {@periodic.keys}
        #Order all keys
        keylist.each do |akey|
          value = self[akey.to_sym]
          self[akey.to_sym] = self.class.class_eval {@periodic[akey].order(value, self[:nodeid])}
        end
      end
    elsif self[:type] != :processed
      #puts "Warning: No constraints defined at at all for event type #{self[:type]}"
    end
  end

  #Private method to display constraint violations when creating new events and checking
  #against previous specifications.
  def constraint_violation(constraint, level, eventtype, value)
    case level
     when :error
      $stderr.puts "Error due to constraint violation on #{constraint} on #{eventtype}: Value is #{value}"
      raise "ConstraintException" 
     when :warning
      puts "Warning due to constraint violation on #{constraint} on #{eventtype}: Value is #{value}"
     else
       $stderr.puts "Sepcification exception necause of wrong level #{level} on #{constraint} on #{eventtype}"       
       raise "SpecificationException" 
    end
  end

  #Assigns value to a new or existing key. Assignment checks that the key is a symbol.
  def []=(key, value)
#less checking, assuming user is a good citizen
#    raise @@keyException unless key.instance_of?Symbol
    super(key, value)
  end
                                                                 
  # Define equality operator. For an in-depth discussion on the 
  # differences of the operators see:
  # http://www.texperts.com/2007/10/16/navigating-the-equality-maze/
  def ==(other)
    return false unless other #if other is nil - note that self cannot be nil!
    return false unless self.length == other.length
    self.each do |key, value|
      return false unless other.has_key?(key)
      return false unless self[key] == other[key]
    end
    return true
  end

  #Checks that other is an event and then compares with ==, else returns false.
  def eql?(other)
      if other.kind_of?(Event)
        return self == other
      else
        return false
      end
  end
  
  #Provdides order based on nodeid, if events have the same nodeid, the order 
  #is determined based on type. If both are the same we say they are equal
  def <=>(other)
    if self[:nodeid]>other[:nodeid]
      return 1
    elsif self[:nodeid]<other[:nodeid]
      return -1
    else
      puts "equal" if $DEBUG
      if self[:type].to_s>other[:type].to_s
        puts "bigger" if $DEBUG
        return 1
      elsif self[:type].to_s<other[:type].to_s
        puts "smaller" if $DEBUG
        return -1
      else 
        return 0
      end
    end
  end

  #Sort event keys based on their string representation.
  def sort
    self.sort_by {|key, value| key.to_s}
  end
  
  #To_s shows that event is actually an event and prints all key value pairs.
  def to_s
    string = "Event: "
    self.sort.each {|key, value| string += "#{key} : #{value}; "}
    string.rstrip!
  end

  private :constraint_violation

end


class Symbol
    #Extends symbol class with a comparison operator, which is based on the interpretation as strings.
    def <=>(other)
      self.to_s <=> other.to_s
    end
end
